# coding=utf8
#  This is part of Scopia Visual Framework
#
#  Fuctions and classes to draw text with prerendered
#  characters.
#
#  Copyright (C) 2008  Scopia Visual Interfaces Systems 
#                      http://www.scopia.es
#
#  author: Alejandro Conty
#  contributors:
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

import Image, ImageFont, ImageDraw
import math
from numpy import *



def prerender(chars):
    """Returns a PIL image with the rendered chars and two dictionaries
       The first one holds a tuple like ((u, v), (u, v)) with the UV coordinates
       for the downleft and upright corners of character for every char
       and the second holds the pixel sizes for them."""
    font = ImageFont.truetype("scopiavf/font/sans.ttf", 64)
    im = Image.new('RGB',(0,0) )
    draw = ImageDraw.Draw( im )
    totalen = 0
    lineheight = 0
    maxwidth = 0
    # we had a lot of problems with some characters not rendering where
    # they should and this value is used as an offset to work around
    # the problem
    safecorr =  draw.textsize( ' ', font = font )[0]/2
    # Sum up all the lengths of the chars
    for c in chars:
        width, height = draw.textsize( c, font = font )
        totalen += width
        lineheight = max( lineheight, height )
        maxwidth = max( maxwidth, width )
    side = int(math.ceil( math.sqrt( len(chars) ) + 1 ))
    avgwidth = totalen / len(chars)
    imW = side * avgwidth
    imH = side * (lineheight + 4)
    im = Image.new('RGBA',(imW,imH), (255, 255, 255, 0))
    draw = ImageDraw.Draw( im )
    offY = 0
    offX = 0
    # This table holds a tuple like ((u, v), (u, v)) with the UV coordinates
    # for the downleft and upright corners of character for every char to be
    # prerendered
    tabla = {}
    # this other table holds the pixel sizes for every char
    sizetabla = {}
    fimW, fimH = float(imW), float(imH)
    for c in chars:
        width, height = draw.textsize( c, font = font )
        tabla[c] = ( (offX/fimW, offY/fimH), ((offX+width)/fimW, (offY+height)/fimH) )
        sizetabla[c] = (width, height)
        # Dirty workaround for J that seems to render outside the square where it should do
        if c == u'j' or c == u'J':
            draw.text( (offX-safecorr, offY), ' '+c, font = font, fill=(255,255,255,255) )
        else:
            draw.text( (offX, offY), c, font = font, fill=(255,255,255,255) )
        offX += width
        if offX > (imW-maxwidth):
            offX = 0
            offY += lineheight + 4
    outW, outH = nextPower(imW), nextPower(imH)
    if outW != imW or outH != imH: im = im.resize( (outW, outH), Image.BILINEAR )
    return (im, tabla, sizetabla)

        
def nextPower( val ):
    "Yes, the same function again"
    p = 1
    resto = 0
    while val != 1:
        resto = (resto << 1) | (val & 1)
        val >>= 1
        p <<= 1
    if resto: p <<= 1
    return p

charset = u'ABCDEFGHIJKLMNÑOPQRSTUVWXYZÁÉÍÓÚÜÀÈÌÒÙabcdefghijklmnñopqrstuvwxyzáéíóúüàèìòù¡!¿?"\',;.:-_*çÇ[]{}=)($%&/\\1234567890 '

class PrerenderedText(object):
    """A prerendered text instance doesn't need to build a new texture.
       It uses the prerendered texture in the context and only builds the
       needed quads to show the selected characters."""

    def __init__(self, context, text, points, align = 'center' ):
        """Init function. If the prerendered characters are still not present
           this builds them in the context.

                context         glContext to use
                text            Text to show
                points          A numpy array with the four corners of the
                                square to use"""
        try:
            # take chars info and texture from the context
            self.texid = context.prerentex_texid
            self.table = context.prerentex_table
            self.charsize = context.prerentex_charsize
        # load if not present
        except: self.loadFont(context)
        corner = points[0]
        U1 = points[1] - points[0]
        #U2 = points[2] - points[3]
        V1 = points[3] - points[0]
        #V2 = points[2] - points[1]
        quads , uvs = self.buildGeom( text, float(sqrt(dot(U1,U1))), float(sqrt(dot(V1,V1))) )
        if align == 'left':
            quads[:,0] -= min(quads[:,0])
        elif align == 'right':
            quads[:,0] += 1.0 - max(quads[:,0])
        #self.quads =(quads[:,0][:,newaxis] * U1 * (1.0 - quads[:,1])[:,newaxis] +
        #             quads[:,0][:,newaxis] * U2 * quads[:,1][:,newaxis] +
        #             quads[:,1][:,newaxis] * V1 * (1.0 - quads[:,0])[:,newaxis] +
        #             quads[:,1][:,newaxis] * V2 * quads[:,0][:,newaxis] + corner )
        self.quads = (quads[:,0][:,newaxis] * U1 + quads[:,1][:,newaxis] * V1) + corner
        self.minX, self.maxX = min(self.quads[:,0]), max(self.quads[:,0])
        self.minY, self.maxY = min(self.quads[:,1]), max(self.quads[:,1])
        self.uvs = uvs

    def draw(self, context):
        "Draw the text (quads) in the given context"
        context.startTextureId( self.texid )
        context.drawQuadArray( self.quads, self.uvs )
        context.endTextureId( self.texid )
        

    def loadFont(self, context):
        "Build the texture with prerendered chars and tables"
        global charset
        im, context.prerentex_table, context.prerentex_charsize = prerender( charset )
        context.prerentex_texid = context.loadTextureImg( im )
        self.texid = context.prerentex_texid
        self.table = context.prerentex_table
        self.charsize = context.prerentex_charsize

    def textsize(self, text):
        "Returns a tuple with (sum of widths, maximum height)"
        #global charsize
        total = 0
        maxh = 0
        for c in text:
            w, h = self.charsize.get( c, (0,0) )
            total += w
            maxh = max(maxh, h)
        return (total, maxh)

    def splitLines( self, text, width ):
        """Splits a text among lines to avoid lines larger than 'width'. Now
           still doesn't handle newlines or any formating.

                text        Text to render
                width       Maximum line length in pixels"""
        spacew, height = self.textsize(' ')
        words = text.split()
        lines = []
        line = ''
        w = 0
        while words:
            if line: word = ' ' + words[0]
            else: word = words[0]
            nextw, nexth = self.textsize(word)
            if (w + nextw) <= width or not line:
                line += word
                w += nextw
                words = words[1:]
            else:
                lines.append( (line, w) )
                line = ''
                w = 0
        if line: lines.append( (line, w) )
        return lines

    def buildGeom( self, text, width, height ):
        """Returns the array of quads and the array of texture coords
           for those quads in order to render the text. Width and height
           units don't matter. The returned quads are in [0,1]x[0,1]
           square (scaling meeded), but are guaranteed to look ok at an 
           aspect ratio of width/height."""
        if type(text) != unicode: text = unicode(text)
        rW, rH = self.textsize( text )
        area = rW * rH
        ratio = float(height) / float(width)
        imW = int(math.sqrt( area / ratio ))
        lines = self.splitLines( text, imW )
        imH = rH * len(lines)
        imW = max( [ t[1] for t in lines ] )
        tratio = float(imH) / float(imW)
        if tratio < ratio: 
            offX, offY = 0, int( (ratio * imW  - imH)*0.5 )
            imH = int( ratio * imW )
        else:
            offX, offY = int( (imH / ratio  -imW)*0.5 ), 0
            imW = int( imH / ratio )
        imWinv = 1.0 / imW
        imHinv = 1.0 / imH
        quads = []
        uvcoords = []
        y =  offY * imHinv + (len(lines)-1)* rH * imHinv
        for i in xrange(len(lines)):
            x = offX * imWinv
            for c in lines[i][0]:
                try: w, h = self.charsize[c]
                except: continue
                w *= imWinv
                h *= imHinv
                minuv, maxuv = self.table[c]
                minu, minv = minuv
                maxu, maxv = maxuv
                quads += [ (x+w, y), (x+w, y+h), (x, y+h), (x, y) ]
                uvcoords += [(maxu, maxv), (maxu, minv), (minu, minv), (minu, maxv) ]
                x += w
            y -= rH * imHinv
        return (array(quads, float32), array(uvcoords, float32))

